﻿using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace MiHoMiao.Mono.UnityPool
{
    /// <summary>
    ///     适用于 Unity 对象的对象池，只要实现了 IPooledObject 的组件都可以实现。
    ///     很遗憾， GameObject 和 Transform 并不满足条件，需要选择它们身上其他满足条件的组件来实现效果。
    /// </summary>
    public static class UnityPool<TValue> where TValue : Component, IPooledObject
    {
        private static readonly Dictionary<Component, (Transform parent, Queue<TValue> list)>
            m_PoolDataDic = new Dictionary<Component, (Transform, Queue<TValue>)>();

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
        private static void InitializeOnLoad() => SceneManager.sceneLoaded += (_, _) => m_PoolDataDic.Clear();

        /// <summary>
        ///     根据 source 来构造或获取一个指定的对象,
        ///     将对象设置为 parent 的子物体并设置好 worldPosition 与 worldRotation,
        ///     然后返回这个物体。强烈建议使用该方法取代所有的 Object.Instantiate() 方法.
        /// </summary>
        public static TValue Get(TValue source, Transform parent = null, Vector3 worldPosition = default,
            Quaternion worldRotation = default)
        {
            TValue newObject;
            if (m_PoolDataDic.ContainsKey(source) && m_PoolDataDic[source].list.Count > 0)
            {
                newObject = m_PoolDataDic[source].list.Dequeue();
                newObject.gameObject.SetActive(true);
                newObject.transform.SetParent(parent);
                newObject.transform.SetPositionAndRotation(worldPosition, worldRotation);
            }
            else
            {
                newObject = Object.Instantiate(source, worldPosition, worldRotation, parent);
                newObject.name = source.name;
            }
            newObject.transform.localScale = Vector3.one;
            newObject.Source = source;
            newObject.OnGet();
            return newObject;
        }
        
        /// <summary>
        ///     警告: 仅适用于 UI 等具有 RectTransform 组件的对象
        ///     根据 source 来构造或获取一个指定的对象,
        ///     将对象设置为 parent 的子物体并设置好 anchoredPosition,
        ///     然后返回这个物体。强烈建议使用该方法取代所有的 Object.Instantiate() 方法.
        /// </summary>
        public static TValue GetUI(TValue source, Transform parent = null, Vector2 anchoredPosition = default)
        {
            TValue newObject;
            RectTransform rectTransform;
            if (m_PoolDataDic.ContainsKey(source) && m_PoolDataDic[source].list.Count > 0)
            {
                newObject = m_PoolDataDic[source].list.Dequeue();
                newObject.gameObject.SetActive(true);
                newObject.transform.SetParent(parent);
                rectTransform = (RectTransform)newObject.transform;
                rectTransform.anchoredPosition = anchoredPosition;
            }
            else
            {
                newObject = Object.Instantiate(source, parent);
                rectTransform = (RectTransform)newObject.transform;
                rectTransform.anchoredPosition = anchoredPosition;
                newObject.name = source.name;
            }
            newObject.transform.localScale = Vector3.one;
            newObject.Source = source;
            newObject.OnGet();
            return newObject;
        }

        /// <summary>
        ///     将一个通过 UnityPool.Get() 实例化的物体入池。
        ///     如果物体并非通过 UnityPool.Get() 实例化，
        ///     则调用 Object.Destroy() （注意：此方法会在下一帧才进行实际销毁操作）来销毁。
        /// </summary>
        public static void Push(TValue target)
        {
            if (!target.Source)
            {
                Object.Destroy(target);
                return;
            }

            if (!m_PoolDataDic.ContainsKey(target.Source))
                m_PoolDataDic[target.Source] =
                    (new GameObject("Pooled-" + target.Source.name).transform, new Queue<TValue>());
            m_PoolDataDic[target.Source].list.Enqueue(target);
            target.gameObject.SetActive(false);
            target.OnRelease();
            target.transform.SetParent(m_PoolDataDic[target.Source].parent);
        }
    }
}